using System;
using System.Collections.Generic;
using System.Linq;
using GeneticAlgorithms.Factories;
using GeneticAlgorithms.Genomes;
using GeneticAlgorithms.Operators.Crossover;
using GeneticAlgorithms.Operators.Fitness;
using GeneticAlgorithms.Operators.Initialization;
using GeneticAlgorithms.Operators.Mating;
using GeneticAlgorithms.Operators.Mutation;
using GeneticAlgorithms.Operators.Selection;
using GeneticAlgorithms.Tools;

namespace GeneticAlgorithms.Populations
{
    /// <summary>
    /// Base class for GA populations.
    /// </summary>
    /// <typeparam name="TGenome">The type of the genome.</typeparam>
    /// <typeparam name="TGene">The type of the gene.</typeparam>
    public abstract class PopulationBase<TGenome, TGene> : IPopulation<TGenome, TGene> where TGenome : IGenome<TGene>
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="PopulationBase&lt;TGenome, TGene&gt;"/> class.
        /// </summary>
        /// <param name="genomes">The genomes the population will start with.</param>
        /// <param name="genomeFactory">The genome factory.</param>
        /// <param name="fitnessProvider">The fitness provider.</param>
        /// <param name="initializer">The initializer.</param>
        /// <param name="selector">The selector.</param>
        /// <param name="mater">The mater.</param>
        /// <param name="crossover">The crossover.</param>
        /// <param name="mutator">The mutator.</param>
        protected PopulationBase(
            IList<TGenome> genomes,
            IGenomeFactory<TGenome> genomeFactory,
            IFitnessProvider<TGenome, TGene> fitnessProvider,
            IGeneticInitializer<TGene> initializer,
            IGeneticSelector<TGenome, TGene> selector,
            IGeneticMater<TGenome, TGene> mater,
            IGeneticCrossover<TGenome, TGene> crossover,
            IGeneticMutator<TGene> mutator)
        {
            Genomes = genomes;
            GenomeFactory = genomeFactory;
            FitnessProvider = fitnessProvider;
            Initializer = initializer;
            Selector = selector;
            Mater = mater;
            Crossover = crossover;
            Mutator = mutator;
            CreateGenomes();
            AveragesHistory = new LinkedList<double>();
            FitnessComparer = FitnessComparer.Default;
            MaxHistoryLength = 5;
        }

        #endregion

        #region Properties and fields

        /// <summary>
        /// Gets the the maxValue history length.
        /// </summary>
        /// <value>The maxValue history length.</value>
        protected int MaxHistoryLength
        {
            get;
            set;
        } 

        /// <summary>
        /// Gets the population's genomes.
        /// </summary>
        /// <value>The genomes.</value>
        protected IList<TGenome> Genomes
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets or sets the genome factory.
        /// </summary>
        /// <value>The genome factory.</value>
        protected internal IGenomeFactory<TGenome> GenomeFactory
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the initializer.
        /// </summary>
        /// <value>The initializer.</value>
        protected IGeneticInitializer<TGene> Initializer
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the fitness provider.
        /// </summary>
        /// <value>The fitness provider.</value>
        protected internal IFitnessProvider<TGenome, TGene> FitnessProvider
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the selector.
        /// </summary>
        /// <value>The selector.</value>
        protected internal IGeneticSelector<TGenome, TGene> Selector
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the crossover.
        /// </summary>
        /// <value>The crossover.</value>
        protected internal IGeneticCrossover<TGenome, TGene> Crossover
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the mutator.
        /// </summary>
        /// <value>The mutator.</value>
        protected internal IGeneticMutator<TGene> Mutator
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the mater.
        /// </summary>
        /// <value>The mater.</value>
        protected internal IGeneticMater<TGenome, TGene> Mater
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the new populationSize of the generation.
        /// </summary>
        /// <value>The new populationSize of the generation.</value>
        protected internal abstract int NewGenerationSize { get; }


        /// <summary>
        /// Gets the averages history.
        /// </summary>
        /// <value>The averages history.</value>
        protected internal LinkedList<double> AveragesHistory 
        { 
            get; 
            private set; 
        }

        /// <summary>
        /// Gets or sets the fitness valueExtractor.
        /// </summary>
        /// <value>The fitness valueExtractor.</value>
        protected internal FitnessComparer FitnessComparer 
        {
            get;
            set;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds an average to the stats history.
        /// </summary>
        /// <param name="currentAverage">The current average.</param>
        protected void AddAverage(double currentAverage)
        {
            AveragesHistory.AddLast(currentAverage);
            while (AveragesHistory.Count > MaxHistoryLength)
            {
                AveragesHistory.RemoveFirst();
            }
        }

        /// <summary>
        /// Creates the genomes in the population.
        /// </summary>
        protected virtual void CreateGenomes()
        {
            for (int i = 0; i < Genomes.Count; ++i)
            {
                Genomes[i] = GenomeFactory.CreateGenome();
            }
        }

        // TODO: refactor this later on, in order to provide other
        // kinds of average calculi
        /// <summary>
        /// Calculates the fitness stats.
        /// </summary>
        protected virtual void CalculateFitnessStats()
        {
            BestFitness = this.Max(g => g.Fitness.Value);
            AverageFitness = this.Average(g => g.Fitness);
            Variance = Math.Sqrt(this.Average(g => Math.Pow(g.Fitness.Value - AverageFitness.Value, 2)));
            AddAverage(AverageFitness.Value);
            if (AveragesHistory.Count > 0)
            {
                double generationAverage = AveragesHistory.Average();
                GenerationVariance = AveragesHistory.Average(v => Math.Pow(v - generationAverage, 2));
            }
            else
            {
                GenerationVariance = null;
            }
        }


        #endregion

        #region IPopulation<TGene> Members

        /// <summary>
        /// Gets the number of elapsed generations.
        /// </summary>
        /// <value>The number of elapsed generations.</value>
        public int Generations
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the average fitness.
        /// </summary>
        /// <value>The average fitness.</value>
        public virtual double? AverageFitness
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the best fitness among the population's genomes.
        /// </summary>
        /// <value>The best fitness.</value>
        public double BestFitness 
        { 
            get; 
            private set; 
        }

        /// <summary>
        /// Gets the genomes variance.
        /// </summary>
        /// <value>The variance.</value>
        public double? Variance 
        { 
            get; 
            private set; 
        }

        /// <summary>
        /// Gets the variance from generation to generation.
        /// </summary>
        /// <value>The generation variance.</value>
        public double? GenerationVariance
        {
            get;
            private set;
        }
	

        /// <summary>
        /// Gets or sets the populationSize of the elite.
        /// </summary>
        /// <value>The populationSize of the elite.</value>
        public virtual int EliteSize
        {
            get;
            set;
        }

        /// <summary>
        /// Initializes the population.
        /// </summary>
        public virtual void Initialize()
        {
            foreach (TGenome genome in this)
            {
                Initializer.Initialize(genome);
                genome.Fitness = null;
            }
            Generations = 0;
            UpdateStats();
        }

        /// <summary>
        /// Performs a single evolutionary step.
        /// </summary>
        public virtual void DoStep()
        {
            #region Selecting the genomes
            IEnumerable<TGenome> selected = Selector.SelectGenomes(this, 
                NewGenerationSize);
            #endregion

            #region Crossing the genomes

            TGenome[] tempGenomes = new TGenome[NewGenerationSize];

            TGenome[] childrenPair =
                new TGenome[Crossover.Cardinality];
            
            int i;

            i = 0;
            foreach (TGenome[] pair in Mater.Mate(selected))
            {
                for (int k = 0; k < childrenPair.Length; ++k)
                    childrenPair[k] = GenomeFactory.CreateGenome();

                Crossover.Cross(pair, childrenPair);
                for (int j = 0; j < childrenPair.Length; ++j, ++i)
                {
                    tempGenomes[i] = childrenPair[j];
                }
            }

            #endregion

            #region Mutating the children genomes and calculating their final fitness
            
            foreach (TGenome genome in tempGenomes)
            {
                Mutator.Mutate(genome);
                genome.Fitness = FitnessProvider.CalculateFitness(genome);
            }
            
            #endregion

            #region Merging the new population in the old one
            Merge(tempGenomes);
            #endregion

            Generations++;
        }

        /// <summary>
        /// Calcs the population fitness.
        /// </summary>
        private void CalcPopulationFitness()
        {
            foreach (TGenome genome in Genomes)
            {
                genome.Fitness = FitnessProvider.CalculateFitness(genome);
            }
        }

        /// <summary>
        /// Gets the best genome in this population.
        /// </summary>
        /// <returns>The fittest genome in the population.</returns>
        IGenomeBase IPopulation.GetBest()
        {
            return GetBest();
        }

        /// <summary>
        /// Gets the best genome in this population.
        /// </summary>
        /// <returns>The fittest genome in the population.</returns>
        public TGenome GetBest()
        {
            int bestPos = 0;

            for (int i = 1; i < Count; ++i)
            {
                if (this[i].Fitness > this[bestPos].Fitness)
                {
                    bestPos = i;
                }
            }

            return this[bestPos];
        }

        /// <summary>
        /// Updates the population stats.
        /// </summary>
        public void UpdateStats()
        {
            if (!Genomes[0].Fitness.HasValue)
            {
                CalcPopulationFitness();
            }
            CalculateFitnessStats();
        }

        /// <summary>
        /// Merges the specified children.
        /// </summary>
        /// <param name="children">The children.</param>
        protected abstract void Merge(TGenome[] children);

        /// <summary>
        /// Sorts all genomes in the population, according to their fitness.
        /// </summary>
        public abstract void Sort();


        #endregion

        /// <summary>
        /// Gets the genome count in the population.
        /// </summary>
        /// <value>The number of genomes that are currently in the population.</value>
        public virtual int Count { get { return Genomes.Count; } }


        #region IEnumerable<TGenome> Members

        /// <summary>
        /// Returns an enumerator that iterates through the collection.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<TGenome> GetEnumerator()
        {
            return Genomes.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return Genomes.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Gets or sets the <see cref="GeneticAlgorithms.Genomes.IGenome&lt;TGene&gt;"/> at the specified index.
        /// </summary>
        /// <value></value>
        public virtual TGenome this[int index]
        {
            get
            {
                return Genomes[index];
            }
            set
            {
                Genomes[index] = value;
            }
        }
    }
}
